package com.mzpai.util.generate;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.mzpai.util.StringUtils;

/**
 * 
 * <b>根据PO生成SQLMap.xml</b><br>
 * 
 * yanglijun 2012-11-14 下午3:39:01
 * 
 * @version 1.0.0
 * 
 */
public class GenerateSQLMap<T> {
  private final int SIZE = 4;
  private final String FILE_URL_PREFIX = "file:/";
  private final String TARGET_CLASSES = "target/classes";
  private final String SRC_MAIN_RESOURCES = "src/main/resources/mybatis";
  private final String XML_TITLE = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>";
  private final String XML_DOCTYPE = "<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\" >";
  @SuppressWarnings("unused")
private final String INSERT = "insert", UPDATE = "update", DELETE = "delete", SELECT = "select", SET = "set", IF = "if", MAP = "map", SQL = "sql", SAVE = "insert", QUERY_BY_ID = "queryById", BASE_COLUMN_LIST = "baseColumnList", INCLUDE = "include", LONG = "long", PO = "model", POJO = "vo", EMPTY = "", SPACES = " ", NEXT_LINE = "\n", JAVA_SUFFIX = ".xml", QUERY_PAGE_COUNT = "queryPageCount", SEARCH_CONDITION = "searchCondition", QUERY_PAGE = "getList", PUBLIC_BEFORE_PAGE = "Public.beforePage", BASE_ATTR = "base.baseAttr", PUBLIC_AFTER_PAGE = "Public.afterPage", WHERE = "where", GET_SEQ_VALUE = "getSeqValue", TIMESTAMP = "TIMESTAMP", RESULTMAP = "resultMap", NUMERIC = "NUMERIC", VARCHAR = "VARCHAR";
  private final String parameterType = "java.lang.String";
  private Class<T> CLAZZ;
  private String CLAZZ_SIMPLE_NAME;
  private String PATH;
  private String CTX;
  private List<String> XML_LINE_LIST = new ArrayList<String>();
  private List<PropertyMapper> BASE_PROPERTY_LIST = new ArrayList<PropertyMapper>();
  private List<PropertyMapper> PROPERTY_LIST = new ArrayList<PropertyMapper>();
  private String CLAZZ_FULL_NAME;
  private String TABLE_NAME;

  public GenerateSQLMap(Class<T> clazz, String talbeName) {
    this.CLAZZ = clazz;
    this.CLAZZ_SIMPLE_NAME = clazz.getSimpleName();
    this.CLAZZ_FULL_NAME = clazz.getPackage().getName() + "." + clazz.getSimpleName();
    TABLE_NAME = talbeName;
    getPath();
    getCtx();
    getSupperProperty(clazz.getSuperclass());
    generateXML();
    fileWriter();
  }

  private void getPath() {
    PATH = CLAZZ.getResource(EMPTY).toString();
    if(PATH != null) {
	    PATH = replace(PATH, FILE_URL_PREFIX, EMPTY);
	    int j = PATH.indexOf(TARGET_CLASSES);
	    int i = PATH.indexOf(addSeparator(PO));
	    if (i < 0) {
	      i = PATH.indexOf(addSeparator(POJO));
	      i = i + POJO.length();
	    } else {
	      i = i + PO.length();
	    }
	    ++i;
	    PATH = PATH.substring(0, j) + SRC_MAIN_RESOURCES + PATH.substring(i);
	    PATH = PATH.replace("", "");
    }
  }

  private void getCtx() {
    CTX = replace(CLAZZ.getName(), "com.sohu.crm.", EMPTY);
    if(CTX != null) {
    	CTX = CTX.substring(0, CTX.indexOf(".")).toUpperCase();
    }
  }

  private void generateXML() {
    addXMLLine(XML_TITLE, 0);
    addXMLLine(XML_DOCTYPE, 0);
    addXMLLine("<mapper namespace=\"com.mzpai.product.mapper." + CLAZZ_SIMPLE_NAME + "Mapper" + "\">", 0);
    generateResultMap();
    addXMLLine(EMPTY, 0);
    generateColumnList();
    addXMLLine(EMPTY, 0);
    
    generateSave();
    addXMLLine(EMPTY, 0);
    generateDelete();
    addXMLLine(EMPTY, 0);
    generateUpadate();
    addXMLLine(EMPTY, 0);
    
    generateQueryById();
    addXMLLine(EMPTY, 0);
   // generateQueryPageCount();
    generateQueryPage();
    addXMLLine(EMPTY, 0);
    generateSearchCondition();
    addXMLLine(EMPTY, 0);
//    generateGetSeqValue();
    addXMLLine("</mapper>", 0);
  }

  private void generateResultMap() {
    addSQLLabel(RESULTMAP, CLAZZ_SIMPLE_NAME, null, null, null, null, null, CLAZZ_FULL_NAME, BASE_ATTR, null, SIZE);
    for (Field field : CLAZZ.getDeclaredFields()) {
      String fieldName = field.getName();
      if ("serialVersionUID".equals(fieldName)) {
        continue;
      }
      PropertyMapper propertyMapper = new PropertyMapper();
      propertyMapper.setProperty(fieldName);
      propertyMapper.setColumn(getColumn(fieldName));
      Type type = field.getType();
      if (type.equals(List.class) || type.toString().indexOf(".crm.") > 0) {
        continue;
      } else if (type.equals(Date.class)) {
        propertyMapper.setJdbcType(TIMESTAMP);
      } else if (type.equals(Long.class) || type.equals(BigDecimal.class) || type.equals(Double.class) || type.equals(Integer.class) || type.equals(Float.class)) {
        propertyMapper.setJdbcType(NUMERIC);
      } else {
        propertyMapper.setJdbcType(VARCHAR);
      }
      addResult(propertyMapper);
      PROPERTY_LIST.add(propertyMapper);
    }
    addSQLEndLabel(RESULTMAP, SIZE);
  }

  private void generateColumnList() {
    addSQLLabel(SQL, BASE_COLUMN_LIST, null, null, null, null, null, null, null, null, SIZE);
    StringBuffer sb = new StringBuffer();
    for (PropertyMapper property : BASE_PROPERTY_LIST) {
     // sb.append("t.");
    	sb.append(" ");
      sb.append(property.getColumn());
      sb.append(", ");
    }
    for (PropertyMapper property : PROPERTY_LIST) {
    //  sb.append("t.");
    	sb.append(" ");
      sb.append(property.getColumn());
      sb.append(",");
    }
    addXMLLine((sb.substring(0, sb.length() - 1)), SIZE * 2);
    addSQLEndLabel(SQL, SIZE);
  }

  private void generateSave() {
    addSQLLabel("insert", SAVE, CLAZZ_FULL_NAME, null, null, null, null, null, null, null, SIZE);

    addXMLLine("INSERT INTO " + TABLE_NAME + " (", SIZE * 2);
    addSQLLabel(INCLUDE, null, null, null, null, BASE_COLUMN_LIST, null, null, null, null, SIZE * 2);
    addXMLLine(") VALUES (", SIZE * 2);
    int m = BASE_PROPERTY_LIST.size() + PROPERTY_LIST.size();
    int i = 0;
    for (PropertyMapper property : BASE_PROPERTY_LIST) {
      addValue(property, ++i != m, SIZE * 2);
    }
    for (PropertyMapper property : PROPERTY_LIST) {
      addValue(property, ++i != m, SIZE * 2);
    }
    addXMLLine(")", SIZE * 2);

    addSQLEndLabel("insert", SIZE);
  }

  private void generateDelete() {
    addSQLLabel(UPDATE, DELETE, parameterType, null, null, null, null, null, null, null, SIZE);

    StringBuffer sb = new StringBuffer("DELETE  from " + TABLE_NAME);
    sb.append(" WHERE ID=#{id,jdbcType=VARCHAR}");
    addXMLLine(sb.toString(), SIZE * 2);

    addSQLEndLabel(UPDATE, SIZE);
  }

  private void generateUpadate() {
    addSQLLabel(UPDATE, UPDATE, CLAZZ_FULL_NAME, null, null, null, null, null, null, null, SIZE);

    addXMLLine("UPDATE " + TABLE_NAME + " ", SIZE * 2);
    addSQLLabel(SET, null, null, null, null, null, null, null, null, null, SIZE * 2);
    for (PropertyMapper property : BASE_PROPERTY_LIST) {
      addSQLLabel(IF, null, null, null, property.getProperty() + " != null", null, null, null, null, null, SIZE * 3);
      addWhere(EMPTY, property, SIZE * 4);
      addSQLEndLabel(IF, SIZE * 3);
    }
    for (PropertyMapper property : PROPERTY_LIST) {
      addSQLLabel(IF, null, null, null, property.getProperty() + " != null", null, null, null, null, null, SIZE * 3);
      addWhere(EMPTY, property, SIZE * 4);
      addSQLEndLabel(IF, SIZE * 3);
    }
    addSQLEndLabel(SET, SIZE * 2);
    addXMLLine("WHERE ID=#{id,jdbcType=VARCHAR}", SIZE * 2);

    addSQLEndLabel(UPDATE, SIZE);
  }

  private void generateQueryById() {
    addSQLLabel(SELECT, QUERY_BY_ID, parameterType , CLAZZ_SIMPLE_NAME, null, null, null, null, null, null, SIZE);

    addXMLLine(SELECT.toUpperCase(), SIZE * 2);
    addSQLLabel(INCLUDE, null, null, null, null, BASE_COLUMN_LIST, null, null, null, null, SIZE * 2);
    addXMLLine("FROM " + TABLE_NAME + "  where ID=#{id,jdbcType=VARCHAR}", SIZE * 2);

    addSQLEndLabel(SELECT, SIZE);
  }

  @SuppressWarnings("unused")
private void generateQueryPageCount() {
    addSQLLabel(SELECT, QUERY_PAGE_COUNT, MAP, null, null, null, null, null, null, LONG, SIZE);

    addXMLLine("SELECT count(1) FROM " + CTX + getColumn(CLAZZ_SIMPLE_NAME) + "_ALL t", SIZE * 2);
    addSQLLabel(INCLUDE, null, null, null, null, SEARCH_CONDITION, null, null, null, null, SIZE * 2);

    addSQLEndLabel(SELECT, SIZE);
  }

  private void generateQueryPage() {
    addSQLLabel(SELECT, QUERY_PAGE, MAP, CLAZZ_SIMPLE_NAME, null, null, null, null, null, null, SIZE);

   // addSQLLabel(INCLUDE, null, null, null, null, PUBLIC_BEFORE_PAGE, null, null, null, null, SIZE * 2);
    addXMLLine(SELECT.toUpperCase(), SIZE * 2);
    addSQLLabel(INCLUDE, null, null, null, null, BASE_COLUMN_LIST, null, null, null, null, SIZE * 2);
    addXMLLine("FROM " + TABLE_NAME + " t where 1=1 ", SIZE * 2);
    addSQLLabel(INCLUDE, null, null, null, null, SEARCH_CONDITION, null, null, null, null, SIZE * 2);
    addXMLLine("ORDER BY id DESC", SIZE * 2);
//    addSQLLabel(INCLUDE, null, null, null, null, PUBLIC_AFTER_PAGE, null, null, null, null, SIZE * 2);

    addSQLEndLabel(SELECT, SIZE);
  }

  private void generateSearchCondition() {
    addSQLLabel(SQL, SEARCH_CONDITION, null, null, null, null, null, null, null, null, SIZE);

  //  addSQLLabel(WHERE, null, null, null, null, null, null, null, null, null, SIZE * 2);
    for (PropertyMapper property : BASE_PROPERTY_LIST) {
      addSQLLabel(IF, null, null, null, property.getProperty() + " != null", null, null, null, null, null, SIZE * 2);
      addWhere("AND", property, SIZE * 3);
      addSQLEndLabel(IF, SIZE * 2);
    }
    for (PropertyMapper property : PROPERTY_LIST) {
      addSQLLabel(IF, null, null, null, property.getProperty() + " != null", null, null, null, null, null, SIZE * 2);
      addWhere("AND", property, SIZE * 3);
      addSQLEndLabel(IF, SIZE * 2);
    }
 //   addSQLEndLabel(WHERE, SIZE * 2);

    addSQLEndLabel(SQL, SIZE);
  }

  @SuppressWarnings("unused")
private void generateGetSeqValue() {
  }

  private void addSQLLabel(String label, String id, String parameterType, String resultMap, String test, String refid, String namespace, String type, String extend, String resultType, int indent) {
    StringBuffer sb = new StringBuffer("<");
    sb.append(label);
    if (StringUtils.isNotEmpty(id)) {
      sb.append(" id=\"");
      sb.append(id);
      sb.append("\"");
    }
    if (StringUtils.isNotEmpty(parameterType)) {
      sb.append(" parameterType=\"");
      sb.append(parameterType);
      sb.append("\"");
    }
    if (StringUtils.isNotEmpty(resultType)) {
      sb.append(" resultType=\"");
      sb.append(resultType);
      sb.append("\"");
    }
    if (StringUtils.isNotEmpty(resultMap)) {
      sb.append(" resultMap=\"");
      sb.append(resultMap);
      sb.append("\"");
    }
    if (StringUtils.isNotEmpty(test)) {
      sb.append(" test=\"");
      sb.append(test);
      sb.append("\"");
    }
    if (StringUtils.isNotEmpty(namespace)) {
      sb.append(" namespace=\"");
      sb.append(namespace);
      sb.append("\"");
    }
    if (StringUtils.isNotEmpty(type)) {
      sb.append(" type=\"");
      sb.append(type);
      sb.append("\"");
    }
    /**
    if (StringUtils.isNotEmpty(extend)) {
      sb.append(" extends=\"");
      sb.append(extend);
      sb.append("\"");
    }
    **/
    if (StringUtils.isNotEmpty(refid)) {
      sb.append(" refid=\"");
      sb.append(refid);
      sb.append("\"/");
    }
    if("insert".equals(label)) {
    	sb.append(" useGeneratedKeys=\"true\" keyProperty=\"id\"");
    }
    sb.append(">");
    addXMLLine(sb.toString(), indent);
  }

  private void addSQLEndLabel(String label, int indent) {
    StringBuffer sb = new StringBuffer("</");
    sb.append(label);
    sb.append(">");
    addXMLLine(sb.toString(), indent);
  }

  private void addValue(PropertyMapper property, boolean addSeparation, int indent) {
    StringBuffer sb = new StringBuffer();
    sb.append("#{");
    sb.append(property.getProperty());
    sb.append(",jdbcType=");
    sb.append(property.getJdbcType());
    sb.append("}");
    if (addSeparation) {
      sb.append(",");
    }
    addXMLLine(sb.toString(), indent);
  }

  private void addWhere(String prefix, PropertyMapper property, int indent) {
    StringBuffer sb = new StringBuffer(prefix);
    sb.append(" ");
    sb.append(property.getColumn());
    sb.append(" = #{");
    sb.append(property.getProperty());
    sb.append(",jdbcType=");
    sb.append(property.getJdbcType());
    sb.append("}");
    if (EMPTY.equals(prefix)) {
      sb.append(",");
    }
    addXMLLine(sb.toString(), indent);
  }

  private void getSupperProperty(Class<?> supperClazz) {
    if (supperClazz != null) {
      getSupperProperty(supperClazz.getSuperclass());
      for (Field field : supperClazz.getDeclaredFields()) {
        String fieldName = field.getName();
        if ("serialVersionUID".equals(fieldName) || "createUserName".equals(fieldName) || "updateUserName".equals(fieldName) || "isReset".equals(fieldName)) {
          continue;
        }
        PropertyMapper propertyMapper = new PropertyMapper();
        propertyMapper.setProperty(fieldName);
        propertyMapper.setColumn(getColumn(fieldName));
        Type type = field.getType();
        if (type.equals(Date.class)) {
          propertyMapper.setJdbcType("TIMESTAMP");
        } else if (type.equals(Long.class) || type.equals(BigDecimal.class) || type.equals(Double.class) || type.equals(Integer.class) || type.equals(Float.class)) {
          propertyMapper.setJdbcType("NUMERIC");
        } else {
          propertyMapper.setJdbcType("VARCHAR");
        }
        BASE_PROPERTY_LIST.add(propertyMapper);
      }
    }
  }

  private void addResult(PropertyMapper propertyMapper) {
    StringBuffer sb = new StringBuffer("<result column=\"");
    sb.append(propertyMapper.getColumn());
    sb.append("\" jdbcType=\"");
    sb.append(propertyMapper.getJdbcType());
    sb.append("\" property=\"");
    sb.append(propertyMapper.getProperty());
    sb.append("\" />");
    addXMLLine(sb.toString(), SIZE * 2);
  }

private String getColumn(String proterty) {
	return proterty.replaceAll("[A-Z]", "_$0").toLowerCase();
  }

  private void fileWriter() {
    File file = new File(PATH);
    if (file.exists() == false) {
      file.mkdirs();
    }
    CLAZZ_SIMPLE_NAME = CLAZZ_SIMPLE_NAME + "Mapper";
    String CLASS_FILE = PATH + CLAZZ_SIMPLE_NAME + JAVA_SUFFIX;
    boolean is = new File(CLASS_FILE).delete();
    if(is) {}
    FileWriter fileWriter =  null;
    try {
      System.out.println(CLASS_FILE);
      fileWriter = new FileWriter(CLASS_FILE, true);
      for (String xmlLine : XML_LINE_LIST) {
        System.out.print(xmlLine);
        fileWriter.write(xmlLine);
      }
      fileWriter.flush();
      
    } catch (Exception e) {
    } finally {
    	try {
    		if(fileWriter != null) {
    			fileWriter.close();
    		}
		} catch (IOException e) {
		}
    }
  }

  private void addXMLLine(String xmlLine, int indent) {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < indent; i++) {
      sb.append(SPACES);
    }
    XML_LINE_LIST.add(sb.toString() + xmlLine + NEXT_LINE);
  }

  private String replace(String source, String oldStr, String newStr) {
    if (source == null || source.length() == 0) {
      return source;
    }
    return source.replace(oldStr, newStr);
  }

  private String addSeparator(String str) {
    return "/" + str + "/";
  }

  private class PropertyMapper {
    private String column;
    private String jdbcType;
    private String property;

    public String getColumn() {
      return column;
    }

    public void setColumn(String column) {
      this.column = column;
    }

    public String getJdbcType() {
      return jdbcType;
    }

    public void setJdbcType(String jdbcType) {
      this.jdbcType = jdbcType;
    }

    public String getProperty() {
      return property;
    }

    public void setProperty(String property) {
      this.property = property;
    }
  }
}
